package com.wingstudio.springcloud.controller.aspect;

import com.alibaba.fastjson.JSONObject;
import com.google.common.collect.Lists;
import com.wingstudio.springcloud.annotation.RedisLock;
import com.wingstudio.springcloud.redis.RedisLockService;
import org.apache.commons.lang.ObjectUtils;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.LocalVariableTableParameterNameDiscoverer;
import org.springframework.expression.EvaluationContext;
import org.springframework.expression.Expression;
import org.springframework.expression.ExpressionParser;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.StandardEvaluationContext;
import org.springframework.stereotype.Component;

import java.io.BufferedReader;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.*;
import java.util.stream.Collectors;

/**
 * @Author ITcz
 * @Data 2021-04-25 - 22:12
 */
@Component
@Aspect
public class RedisLockAspect {

    @Autowired
    private RedisLockService redisLockService;

    private static Logger logger = LoggerFactory.getLogger(RedisLockAspect.class);

    private static final String REDIS_SET_SUCCESS = "OK";

    /**
    * 默认值为null
    * */
    private static ThreadLocal<HashMap<String, Integer>> lock = new ThreadLocal();

    private void threadLockAdd(HashMap map) {
        if (null == lock.get()) {
            lock.set(map);
        }
        else {
            lock.get().putAll(map);
        }
    }

    @Around("@annotation(RedisLock)")
    public Object lockAround(ProceedingJoinPoint joinPoint, RedisLock RedisLock) throws Throwable {
        System.out.println("进入切面了！！！！！！！！！！！！！！！！！");
        //SpEL ExpressionParser 接口用于解析表达式字符串
        ExpressionParser parser = new SpelExpressionParser();
        //用于获取方法参数定义名字
        LocalVariableTableParameterNameDiscoverer discoverer = new LocalVariableTableParameterNameDiscoverer();
        //EvaluationContext 接口定义了获取属性、方法、域字段解析器及类型转换器
        //它的实现类 StandardEvaluationContext 使用的是反射机制来操作对象
        EvaluationContext context = new StandardEvaluationContext();

        //通过joinPoint获取被注解方法的形参
        Object[] args = joinPoint.getArgs();
        //通过joinPoint获取被注解方法
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        //LocalVariableTableParameterNameDiscoverer获取方法形参名数组
        String[] params = discoverer.getParameterNames(signature.getMethod());
        //给上下文赋值
        for (int len = 0; len < params.length; len++) {
            context.setVariable(params[len], args[len]);
        }
        //表达式从上下文中计算出实际参数值
        /*如:
            @annotation(key="#student.name")
             method(Student student)
             那么就可以解析出方法形参的某属性值，return "xiaoming";
          */
        Expression expression = parser.parseExpression(RedisLock.key());
        String key = expression.getValue(context, String.class);
        //加锁时间最短为10秒
        int lockTime = RedisLock.lockLeaseTime() > 10 ? RedisLock.lockLeaseTime() : 10;
        //尝试获取锁的时间默认为0.3s
        double waitTime = RedisLock.timeout() > 0 ? RedisLock.timeout() : 0;

        String  prefix = RedisLock.prefix();
        if ("".equals(prefix)) {
            //如果没有定义prefix，则使用方法名作为prefix
            prefix = ((MethodSignature) joinPoint.getSignature()).getMethod().getName();
        }
        String realKey = prefix + ":" + key;
        //模仿Redisson使用CPU序列号+线程ID作为requestId，如果并发量特别高可以使用雪花算法
        String requestId = getCpuId() + "-" + Thread.currentThread().getId();
        long startTime = System.currentTimeMillis();
        long endTime = System.currentTimeMillis() + (long) waitTime * 1000;
        //尝试去获取锁
        try {
            do {
                //如果有锁则尝试重入
                if (null != redisLockService.get(realKey)) {
                    //如果不是第一次获得锁，实现可重入性
                    if (null != lock.get() && lock.get().containsKey(realKey)) {
                        //TODO 重入后守护线程的存在时间应该刷新！!
                        HashMap map = lock.get();
                        map.replace(realKey, (Integer) map.get(realKey) + 1);
                        logger.info("线程重入锁成功,锁的名称为{},当前LockCount为{}", realKey, map.get(realKey));
                        //守护线程的时间刷新
                        //执行业务
                        Object returnObject = joinPoint.proceed(args);
                        return returnObject;
                    }
                }
                //第一次获得锁，设置ThreadLocal信息
                HashMap<String, Integer> map = new HashMap<>();
                //map的key为锁的realKey，value为持有数
                map.put(realKey, 1);
                threadLockAdd(map);
                if (redisLockService.lock(realKey, requestId, lockTime)) {
                    if (logger.isInfoEnabled()) {
                        logger.info("获得锁成功,方法名为{},参数为{}", joinPoint.getSignature(),
                                String.join("-", Lists.newArrayList(args).stream().map(obj -> JSONObject.toJSONString(ObjectUtils.defaultIfNull(obj, "null")))
                                        .collect(Collectors.toList())));
                    }
                    //初始化守护线程的内部参数
                    SurvivalClamProcessor survivalClamProcessor
                            = new SurvivalClamProcessor(realKey, requestId, lockTime);
                    Thread survivalThread = new Thread(survivalClamProcessor);
                    survivalThread.setDaemon(Boolean.TRUE);
                    //启动守护线程
                    survivalThread.start();
                    //执行业务
                    Object returnObject = joinPoint.proceed(args);
                    //设置守护线程的关闭标记
                    survivalClamProcessor.stop();
                    //中断sleep状态，保证线程及时销毁
                    survivalThread.interrupt();
                    return returnObject;
                }
                //线程最长睡眠0.1s
                int sleepTime = Math.min(100, (int) waitTime * 100);
                if (logger.isDebugEnabled()) {
                    logger.debug("当前无法获得锁,本次等待{}ms,方法名为{},参数为{}", sleepTime, joinPoint.getSignature(),
                            String.join("-", Lists.newArrayList(args).stream().map(obj -> JSONObject.toJSONString(ObjectUtils.defaultIfNull(obj, "null")))
                                    .collect(Collectors.toList())));
                }
                Thread.sleep(sleepTime);
            } while (System.currentTimeMillis() <= endTime);
            if (logger.isInfoEnabled()) {
                logger.info("获得锁失败,放弃等待,之前共等待{}ms,方法将不执行,方法名为{},参数为{}", System.currentTimeMillis() - startTime, joinPoint.getSignature()
                        , String.join("-", Lists.newArrayList(args).stream().map(Object::toString)
                                .collect(Collectors.toList())));
            }
            //轮询实现给锁延时
            //获取锁失败，不执行目标方法
            return null;
        } finally {
            //如果是第一次获得的锁则释放redis锁
            HashMap map = lock.get();
            Integer lockCount = (Integer) map.get(realKey);
            if (lockCount <= 0) {throw new RuntimeException("锁的持有数小于'1'！");}
            if (1 == lockCount) {
                //尝试释放锁
                redisLockService.releaseDistributedLock(realKey, requestId);
            }
            //如果是重入锁，则status-1
            map.replace(realKey, lockCount - 1);
        }

    }

    /**
     *  给锁续时间的守护线程
     */
    public class SurvivalClamProcessor implements Runnable {

        private static final int REDIS_EXPIRE_SUCCESS = 1;

        private static final int Delay_Times = 3;

        private int count = 1;

        public void setCount(int count) {this.count = count;}

        SurvivalClamProcessor(String realKey, String value, int lockTime) {
            this.realKey = realKey;
            this.value = value;
            this.lockTime = lockTime;
            this.signal = Boolean.TRUE;
        }
        private String realKey;

        private String value;

        private int lockTime;

        //线程关闭的标记
        private volatile Boolean signal;

            void stop() {
            this.signal = Boolean.FALSE;
        }

        @Override
        public void run() {
            int waitTime = lockTime * 1000  / 3;
//            int count = 1;
            //续时操作达到三次或者业务已经执行完成，则关闭守护线程
            while (count <= Delay_Times && Boolean.TRUE.equals(signal)) {
                try {
                    Thread.sleep(waitTime);
                    //给锁续时
                    if (redisLockService.expandLockTime(realKey, value, lockTime) == REDIS_EXPIRE_SUCCESS) {
                        if (logger.isInfoEnabled()) {
                            logger.info("expandLockTime 成功，本次等待{}ms，将重置锁超时时间重置为{}s,其中realKey为{}", waitTime, lockTime, realKey);
                        }
                    } else {
                        if (logger.isInfoEnabled()) {
                            logger.info("expandLockTime 失败，将导致SurvivalClamConsumer中断");
                        }
                        this.stop();
                    }
                } catch (InterruptedException e) {
                    if (logger.isInfoEnabled()) {
                        logger.info("SurvivalClamProcessor 处理线程被强制中断");
                    }
                } catch (Exception e) {
                    logger.error("SurvivalClamProcessor run error", e);
                }
                //续时次数加一
                count++;
            }
            count = 1;
            if (logger.isInfoEnabled()) {
                logger.info("SurvivalClamProcessor 处理线程已停止");
            }
        }
    }


    /**
    * 获取CPU序列号作为机器的唯一标识
    * */
    public  String getCpuId() throws Exception {
        String cpuId;
        // 获取当前操作系统名称
        String os = System.getProperty("os.name");
        os = os.toUpperCase();
//        System.out.println(os);
        // linux系统用Runtime.getRuntime().exec()执行 dmidecode -t processor 查询cpu序列
        // windows系统用 wmic cpu get ProcessorId 查看cpu序列
        if ("LINUX".equals(os)) {
            cpuId = getLinuxCpuId("dmidecode -t processor | grep 'ID'", "ID", ":");
        } else {
            cpuId = getWindowsCpuId();
        }
        return cpuId.toUpperCase().replace(" ", "");
    }

    /**
     * 获取linux系统CPU序列
     */
    public String getLinuxCpuId(String cmd, String record, String symbol) throws Exception {
        String execResult = executeLinuxCmd(cmd);
        String[] infos = execResult.split("\n");
        for (String info : infos) {
            info = info.trim();
            if (info.indexOf(record) != -1) {
                info.replace(" ", "");
                String[] sn = info.split(symbol);
                return sn[1];
            }
        }
        return null;
    }

    public String executeLinuxCmd(String cmd) throws Exception {
        Runtime run = Runtime.getRuntime();
        Process process;
        process = run.exec(cmd);
        InputStream in = process.getInputStream();
        BufferedReader bs = new BufferedReader(new InputStreamReader(in));
        StringBuffer out = new StringBuffer();
        byte[] b = new byte[8192];
        for (int n; (n = in.read(b)) != -1; ) {
            out.append(new String(b, 0, n));
        }
        in.close();
        process.destroy();
        return out.toString();
    }

    /**
     * 获取windows系统CPU序列
     */
    public String getWindowsCpuId() throws Exception {
        Process process = Runtime.getRuntime().exec(
                new String[]{"wmic", "cpu", "get", "ProcessorId"});
        process.getOutputStream().close();
        Scanner sc = new Scanner(process.getInputStream());
        sc.next();
        String serial = sc.next();
        return serial;
    }

}
